import Context from './context.js';
import _ from 'lodash';

Context(({ funcGetSymbolDailPrice, funGetSymbols, socket, utils, funGetPositions, openLong, openShort, getSide, config, getTrades, getBalance }) => {
  const notice = new utils.Notice({
      token: '9a13ecb3-8414-473f-a207-80e80ef3ad35'
  });
  // 记录每个交易对的K线收盘数据，key为symbol，value为{[klineEndTime]: klineData}
  const closedKlines = {};
  // 记录每个币种上次平仓时间
  const lastTradeTime = {};

  // 判断是否满足建仓条件
  async function canOpenPosition(symbol, Klines) {
    // 建仓
    const nowKline = Klines[0];
    const prevKline = Klines[1];
    const prev2Kline = Klines[2];
    // 判断是否达到开仓的条件
    // 涨幅未达标
    const prevRate = prevKline.rate;
    const prev2Rate = prev2Kline ? prev2Kline.rate : 0;
    const nowRate =  nowKline.rate;

    // 判断是否在持仓中
    const positions = funGetPositions();
    // 判断当前币种是否在持仓中
    if (positions.some(p => p.symbol === symbol)) {
        return false;
    }

    // 判断是否在冷静期
    if (lastTradeTime[symbol]) {
        const diff = (new Date().getTime() - lastTradeTime[symbol]) / 60000;
        if (diff < config.cooldownMinutes) {
            console.log([ '冷静期', symbol, diff ]);
            return false;
        }
    }
    // 判断 8 个小时内的亏损次数
    const trades = getTrades();
    const symbolTrades = _.get(trades, `symbols.${symbol}`, []);
    // 8小时内的亏损次数
    const lossTimes = symbolTrades.filter(t => t.rp < 0 &&
            t.timestamp > new Date().getTime() - 1000 * 60 * 60 * 8).length;
    // 如果亏损次数大于等于2次，则不建仓
    if (lossTimes >= 2) {
        // console.log([ 'lossTimes', lossTimes, symbol ]);
        // return false;
    }

    // 预加载当天的开盘价
    if (nowRate > config.gainTriggerPct || ((nowRate + prevRate > (config.gainTriggerPct + 1)))) {
      await funcGetSymbolDailPrice(symbol);
    }

    let side = getSide();
    let dailPrices = null;
    if (side === 'long') {
      // 前柱不能超过15个点
      if (prevKline.rate > 15) {
          return false;
      }
      // 涨幅不达标
      if (prevRate < config.gainTriggerPct && ((prevRate + prev2Rate) < (config.gainTriggerPct + 1))) {
          return false;
      }
      dailPrices = await funcGetSymbolDailPrice(symbol);
      // 检查是否成功获取到数据
      if (!dailPrices || !Array.isArray(dailPrices)) {
          return false;
      }
      // 0 时区跟 8 时区的涨幅超过任意是否超过15%
      if (! dailPrices.filter((d) => (nowKline.close - d.open) / d.open * 100 > 15).length) {
        return false;
      }
    } else if (side === 'short') {
      // 涨幅不达标
      if (prevRate < config.gainTriggerPct) {
          return false;
      }
      const dailPrices = await funcGetSymbolDailPrice(symbol);
       // 检查是否成功获取到数据
      if (!dailPrices || !Array.isArray(dailPrices)) {
          return false;
      }
      // 0 时区涨幅大于5%
      if ((nowKline.close - dailPrices[0].open) / dailPrices[0].open * 100 > 5) {
        return false;
      }
    } else {
      return false;
    }

    let openResult = null;
    // 建仓
    const quantity = getBalance() * 1 / nowKline.close;
    if (side === 'long') {
        openResult = await openLong(symbol, quantity, config.takeProfitPct, config.stopLossPct) ;
    } else {
        openResult = await openShort(symbol, quantity, config.takeProfitPct, config.stopLossPct) ;
    }

    if (openResult) {
        lastTradeTime[symbol] = new Date().getTime();
        notice.bot.sendMsg(`[${config.name}]: ${symbol} ${side} 下单成功 ${utils.formatBeijingDateTime(new Date())} \n ${dailPrices
            .map((d) => `${d.openTime}:${((nowKline.close - d.open) / d.open * 100).toFixed(2)}%`).join('\n')}`);
    } else {
        // 如果下单失败，记录日志
        console.log(['建仓失败', symbol]);
    }
  }

  // K线数据处理函数
  let onKline = async (data) => {
    const { symbol, k } = data;
    const now = Number(k.t);
    // 初始化该symbol的K线收盘数据
    closedKlines[symbol]= closedKlines[symbol] || {};

    // 记录本根K线的收盘数据，key为K线结束时间
    closedKlines[symbol][k.t] = {
      open: data.open,
      close: data.close,
      high: data.high,
      time: data.time,
      rate: data.rate,
      closeToHigh: data.closeToHigh,
    };
    // 按时间倒序排序
    const times = Object.keys(closedKlines[symbol]).map(Number).sort((a, b) => b - a);

    // 没有持仓时，尝试建仓（必须有上一根K线的收盘数据）
    if (times.length >= 2) {
        await canOpenPosition(symbol, times.map(i => closedKlines[symbol][i]));
    }

    // 只保留最近5根K线，防止内存泄漏（从后往前删）
    if (times.length > 5) {
        const removeCount = times.length - 5;
        for (let i = 0; i < removeCount; i++) {
            delete closedKlines[symbol][times[times.length - 1 - i]];
        }
    }
  };
  // 记录已加载的币种
  let loadedSymbols = {};
  let loadedSymbolsTimer = null;
  let loadSymbols = async () => {
    console.log('loadSymbols ' + new Date().toISOString());
    const symbols = await funGetSymbols();
    // 找出未加载的币种
    const ts = symbols.filter(i => !loadedSymbols[i.symbol]);
    console.log(`loadSymbols: new ${ts.length} symbols`);
    (_.chunk(ts.map(i => `${i.symbol.toLowerCase()}_perpetual@continuousKline_3m`), 200))
            .forEach((chunk, i) => {
                // 发送订阅 continuousKline_3m 数据的请求
                socket.send(JSON.stringify({
                    method: 'SUBSCRIBE',
                    params: chunk,
                    id: _.now() + i,
                }));
            });
    if (loadedSymbolsTimer) {
        clearTimeout(loadedSymbolsTimer);
    }
    // 1小时后重新加载
    loadedSymbolsTimer = setTimeout(loadSymbols, 1000 * 60 * 60);
  }
  // WebSocket 连接打开时的回调
  socket.on('open', async () => {
    await loadSymbols();
  });
  // WebSocket 收到消息时的回调
  socket.on('message', async function (data) {
    try {
      const info = JSON.parse(data);
      // 判断是否为 continuous_kline 类型的数据流
      if (info.stream && info.data.e === 'continuous_kline') {
        if (info.data.k.i !== '3m') {
            return;
        }
        const symbol = info.data.ps; // 交易对
        const rate = (info.data.k.c - info.data.k.o) / info.data.k.o * 100;
        loadedSymbols[symbol] = true;
        const time = utils.formatBeijingDateTime(new Date(info.data.k.t));
        const high = info.data.k.h;
        const close = info.data.k.c;
        const open = info.data.k.o;
        const closeToHigh = ((high - close) / open) * 100;
        await onKline({ ...info.data, symbol, rate, time, closeToHigh, high, close, open });
      } else {
        console.log(info);
      }
    } catch (error) {
      // 解析消息出错，打印错误信息
      console.error(new Date().toISOString(), 'Error parsing message:', error);
    }
  });
});
